package util;

import java.text.SimpleDateFormat;

import java.util.Calendar;

/**
 * 
 * @author wim Vandebriel, Hans Brems
 * The Datum class allows basic date manipulations and calculations
 * 
 */
public class Date {
	private final static double MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
	private final static double AVERAGE_MILLIS_PER_YEAR = 365.24 * MILLIS_PER_DAY;
	private final static double AVERAGE_MILLIS_PER_MONTH = AVERAGE_MILLIS_PER_YEAR / 12;

	private Calendar calendar;

	/**
	 * Default constructor initializes the Datum object to the current date.
	 */
	public Date() {
		calendar = Calendar.getInstance();
	}

	/**
	 * Initializes the datum object to date passed as an argument.
	 * 
	 * @param date
	 *            The date the datum will be set to.
	 */
	public Date(java.util.Date date) {
		this.calendar = Calendar.getInstance();
		this.calendar.setTime(date);
	}

	/**
	 * Initializes the Datum object using the 3 arguments (day, month, year)
	 * 
	 * @param day
	 *            The day of month.
	 * @param month
	 *            The month of year.
	 * @param year
	 *            The year.
	 * @throws Exception
	 *             when the arguments cannot be parsed to a valid Datum object.
	 */
	public Date(int day, int month, int year) throws Exception {
		if (!setDate(day, month, year)) {
			throw new Exception(String.format(
					"Failed to parse day : %d, month : %d, year : %d", day,
					month, year));
		}
	}

	/**
	 * Initializes the Datum object using a string in format dd/MM/yyyy
	 * 
	 * @param dateString
	 *            A date string in format dd/MM/yyy
	 * @throws Exception
	 *             When the date string cannot be parsed to a valid Datum
	 *             object.
	 */
	public Date(String dateString) throws Exception {
		String[] strs = dateString.split("/");
		if (strs[1].length() != 2) {
			throw new Exception("Month needs to be 2 characters long");
		}
		if (strs[2].length() != 4) {
			throw new Exception("Year needs to be 4 characters long");
		}
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
		this.calendar = Calendar.getInstance();
		this.calendar.setTime(sdf.parse(dateString));
	}

	/**
	 * Getter method for the Calendar object behind Datum
	 * 
	 * @return a Calendar object initialized with the Datum date
	 */
	protected Calendar getDate() {
		return this.calendar;
	}

	/**
	 * Sets the Datum object using the 3 arguments (day, month, year)
	 * 
	 * @param day
	 *            The day of month.
	 * @param month
	 *            The month of year.
	 * @param year
	 *            The year.
	 * @return true if the values where set correctly.
	 * @throws Exception
	 */
	public boolean setDate(int day, int month, int year) {
		boolean result = false;
		try {
			this.calendar = Calendar.getInstance();
			this.calendar.set(Calendar.DATE, day);
			this.calendar.set(Calendar.MONTH, month - 1);
			this.calendar.set(Calendar.YEAR, year);
			result = true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}

	private String getFormattedDate(String format) {

		SimpleDateFormat sdf = new SimpleDateFormat(format);
		return sdf.format(this.calendar.getTime());
	}

	/**
	 * 
	 * @return The Datum object in format yyyy/MM/dd.
	 */
	public String getDateAmericanFormat() {
		return this.getFormattedDate("yyyy/MM/dd");

	}

	/**
	 * 
	 * @return The Datum object in format dd/MM/yyyy.
	 */
	public String getDateEuropeanFormat() {
		return this.getFormattedDate("dd/MM/yyyy");
	}

	/**
	 * returns the Datum in format dd MMMM yyyy.
	 */
	public String toString() {
		return this.getFormattedDate("dd MMMM yyyy");

	}

	public boolean equals(Object o) {
		return (o instanceof Date && ((Date) o).getDate().equals(this.calendar));
	}

	/**
	 * Compares two datum objects.
	 * 
	 * @param d
	 *            the datum argument used in the comparison
	 * @return the value 0 if the date represented by the argument is equal to
	 *         the date represented by this Datum; a value less than 0 if the
	 *         date of this Datum is before the date represented by the
	 *         argument; and a value greater than 0 if the date of this Datum is
	 *         after the date represented by the argument.
	 */
	public int compareTo(Date d) {
		if (this.calendar.get(Calendar.YEAR) != d.getDate().get(Calendar.YEAR))
			return this.calendar.get(Calendar.YEAR) - d.getDate().get(Calendar.YEAR);
		if (this.calendar.get(Calendar.MONTH) != d.getDate().get(Calendar.MONTH))
			return this.calendar.get(Calendar.MONTH) - d.getDate().get(Calendar.MONTH);
		return this.calendar.get(Calendar.DAY_OF_MONTH)
				- d.getDate().get(Calendar.DAY_OF_MONTH);
	}

	/**
	 * Returns whether this Datum represents a date before the date represented
	 * by the specified Datum
	 * 
	 * @param d
	 *            the Datum used in the comparison.
	 * @return true if this Datum is before the Datum specified with the
	 *         argument.
	 */
	public boolean smallerThen(Date d) {
		return compareTo(d) < 0;
	}

	/**
	 * 
	 * @param d
	 *            the Datum used in the comparison.
	 * @return the difference in years between the Datum and the datum passed as
	 *         argument
	 */
	public int differenceInYears(Date d) {
		return (int) ((this.calendar.getTime().getTime() - d.getDate().getTime()
				.getTime()) / AVERAGE_MILLIS_PER_YEAR);
	}

	/**
	 * 
	 * @param d
	 *            the Datum used in the comparison.
	 * @return the difference in months between the Datum and the datum passed
	 *         as argument
	 */
	public int differenceInMonths(Date d) {
		return (int) ((this.calendar.getTime().getTime() - d.getDate().getTime()
				.getTime()) / AVERAGE_MILLIS_PER_MONTH);
	}

	/**
	 * 
	 * @param d
	 *            the Datum used in the comparison.
	 * @return the difference in days between the Datum and the datum passed as
	 *         argument
	 */
	public int differenceInDays(Date d) {
		return (int) ((this.calendar.getTime().getTime() - d.getDate().getTime()
				.getTime()) / MILLIS_PER_DAY);
	}
	
	/**
	 * 
	 * @return the year of the Date
	 */
	public int getYear(){
		return calendar.get(Calendar.YEAR);
	}
}
